package net.prasenjit.test.core.auth;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.ldap.AuthenticationException;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.ldap.authentication.BindAuthenticator;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class CustomLdapBindAuthenticator extends BindAuthenticator {

    private static final Logger logger = LogManager.getLogger();

    private boolean retrieveGoldSalt = false;
    private LdapTemplate ldapTemplate;

    public CustomLdapBindAuthenticator(BaseLdapPathContextSource contextSource) {
        super(contextSource);
    }

    @Override
    public DirContextOperations authenticate(Authentication authentication) {
        if (retrieveGoldSalt) {
            logger.debug("Using gold salt method to modify password");
            String userName = authentication.getName();
            for (String userDn : getUserDns(userName)) {
                try {
                    DirContextAdapter context = (DirContextAdapter) ldapTemplate.lookup(userDn);
                    if (context != null) {
                        String md5Salt = context.getStringAttribute("md5Salt");
                        if (md5Salt != null) {
                            logger.debug("md5Salt retrieved");
                            String encryptedPassword = goldMd5((String) authentication.getCredentials(), md5Salt);
                            Authentication auth = new UsernamePasswordAuthenticationToken(userName, encryptedPassword);
                            return super.authenticate(auth);
                        }
                    }
                } catch (NameNotFoundException e) {
                    // suppress exception to continue with other provider and
                    // without salt
                }
            }
        }
        return super.authenticate(authentication);
    }

    private String goldMd5(String plainTextPassword, String saltString) {
        StringBuffer hexString = new StringBuffer();
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] messageDigest = md.digest((plainTextPassword + saltString).getBytes());
            for (int i = 0; i < messageDigest.length; i++) {
                if ((0xF0 & messageDigest[i]) == 0)
                    hexString.append('0');
                hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
            }
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
        return hexString.toString();
    }

    @Override
    protected void handleBindException(String userDn, String username, Throwable cause) {
        if (cause instanceof AuthenticationException) {
            String errMessage = cause.getMessage();

            if (errMessage.matches(".*locked.*") || errMessage.matches(".*Locked.*")
                    || errMessage.matches(".*Blocked.*") || errMessage.matches(".*Bocked.*")) {
                throw new LockedException(errMessage);
            }
        }
        System.out.println(cause);
    }

    public void setRetrieveGoldSalt(boolean retrieveGoldSalt) {
        this.retrieveGoldSalt = retrieveGoldSalt;
    }

    public void setLdapTemplate(LdapTemplate ldapTemplate) {
        this.ldapTemplate = ldapTemplate;
    }

}
